package com.yulinlin.web3j.domain;

import lombok.SneakyThrows;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;

import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Int256;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.admin.methods.response.PersonalUnlockAccount;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 实现了接口的币种
 */
public class EIP20InterfaceToken implements EIP20Interface {

    private String contractAddress;

    private Web3j web3 ;

    private ETHSubject subject;

    public EIP20InterfaceToken(String contractAddress, Web3j web3, ETHSubject subject) {
        this.contractAddress = contractAddress;
        this.web3 = web3;
        this.subject = subject;
    }

    public String getContractAddress() {
        return contractAddress;
    }

    private Credentials getCredentials(){
        return subject.getCredentials();
    }

    private String getAddress(){
        return subject.getCredentials().getAddress();
    }

    @SneakyThrows
    private List call( Function function){
        String data = FunctionEncoder.encode(function);

        String address = getAddress();

        Transaction transaction = Transaction.createEthCallTransaction(address, contractAddress, data);

        EthCall ethCall =    web3.ethCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
        List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
        List value =  results.stream().map(v -> v.getValue()).collect(Collectors.toList());

        return value;

    }

    @Override
    public String name() {

        String methodName = "name";
        List inputParameters = new ArrayList<>();
        List outputParameters = new ArrayList<>();

        TypeReference typeReference = new TypeReference() {
        };
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);

        return (String)call(function).get(0);
    }

    @Override
    public String symbol() {
        String methodName = "symbol";
        List inputParameters = new ArrayList<>();
        List outputParameters = new ArrayList<>();

        TypeReference typeReference = new TypeReference() {
        };
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);

        return (String)call(function).get(0);

    }

    @Override
    public BigInteger totalSupply() {
        String methodName = "totalSupply";
        List inputParameters = new ArrayList<>();
        List outputParameters = new ArrayList<>();

        TypeReference typeReference = new TypeReference() {
        };
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);

        return (BigInteger)call(function).get(0);


    }

    @Override
    public BigInteger balanceOf(String fromAddress) {
        String methodName = "balanceOf";
        List inputParameters = new ArrayList<>();
        List outputParameters = new ArrayList<>();
        if(fromAddress == null){
            fromAddress = getAddress();
        }
        Address address = new Address(fromAddress);
        inputParameters.add(address);

        TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {};
        outputParameters.add(typeReference);
        Function function = new Function(methodName, inputParameters, outputParameters);


        return (BigInteger)call(function).get(0);
    }

    @Override
    public boolean transfer(String to, BigInteger value) {
        String methodName = "transfer";
        List inputParameters = new ArrayList<>();
        List outputParameters = new ArrayList<>();

        inputParameters.add(new Address(to));
        inputParameters.add(value);

        TypeReference typeReference = new TypeReference() {};
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);

        return (boolean)call(function).get(0);
    }

    @Override
    public boolean transferFrom(String from, String to, BigInteger value) {
        String methodName = "transferFrom";
        List inputParameters = new ArrayList<>();
        List outputParameters = new ArrayList<>();

        inputParameters.add(new Address(from));
        inputParameters.add(new Address(to));
        inputParameters.add(value);

        TypeReference typeReference = new TypeReference() {};
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);

        return (boolean)call(function).get(0);
    }

    @Override
    public boolean approve(String spender, BigInteger value) {
        String methodName = "approve";
        List inputParameters = new ArrayList<>();
        List outputParameters = new ArrayList<>();

        inputParameters.add(new Address(spender));
        inputParameters.add(value);

        TypeReference typeReference = new TypeReference() {};
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);

        return (boolean)call(function).get(0);
    }

    @Override
    public BigInteger allowance(String owner, String spender) {
        String methodName = "allowance";
        List inputParameters = new ArrayList<>();
        List outputParameters = new ArrayList<>();

        inputParameters.add(new Address(owner));
        inputParameters.add(new Address(spender));

        TypeReference typeReference = new TypeReference() {};
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);

        return (BigInteger)call(function).get(0);
    }
}
